﻿using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using System;
using Xceed.Wpf.AvalonDock.Layout;

namespace NFox.Pyeditor.Runtime
{
    public class Debugger : IDisposable
    {

        #region Trace

        PyIdeWindow _win;

        public Debugger(PyIdeWindow win)
        {
            _win = win;
        }

        private TracebackDelegate Trace(TraceBackFrame frame, string result, object payload)
        {
            if (Enabled)
            {
                var bt = new BreakPoint(frame);
                if (!bt.FileName.StartsWith("<"))
                {
                    switch (CommandType)
                    {
                        case CommandTypes.Start:
                            if (frame.f_code.co_name == _mainfunc)
                            {
                                _mbt = bt;
                                Wait(frame, result, payload);
                            }
                            break;
                        case CommandTypes.Continue:
                            if (BreakPoints.Contains(bt))
                                Wait(frame, result, payload);
                            break;
                        case CommandTypes.Setp:
                            Wait(frame, result, payload);
                            break;
                        case CommandTypes.Next:
                            if (bt.IsNext(_cbt) || _cbt.AtEndLine && !_cbt.IsNext(_mbt))
                                Wait(frame, result, payload);
                            break;
                        case CommandTypes.Return:
                            if(bt.Equals(new BreakPoint(_cbt.BackFrame)))
                                Wait(frame, result, payload);
                            break;
                    }
                }
            }
            return Trace;
        }

        private void Wait(TraceBackFrame frame, string result, object payload)
        {

            _cbt = new BreakPoint(frame);
            var code = _win.ScrollTo(_cbt);

            Types.KeywordList kwlst = new Types.KeywordList();
            foreach (var kv in (PythonDictionary)frame.f_locals)
            {
                dynamic v = kv.Value;
                if (v is ClosureCell)
                {
                    try { v = v.cell_contents; }
                    catch { v = null; }
                    kwlst.AddVariant(kv.Key.ToString(), v);
                }
            }
            _win.locvarlist.ItemsSource = kwlst.Children;

            CommandType = CommandTypes.Wait;
            while (CommandType == CommandTypes.Wait)
                Utils.DoEvents();
            if(CommandType == CommandTypes.Quit)
                throw new Exception("Debuger Quit!");

        }

        public enum CommandTypes
        {
            Start,
            Continue,
            Setp,
            Next,
            Return,
            Quit,
            Wait
        }

        public CommandTypes CommandType { get; private set; }

        public void Start(string func)
        {
            Enabled = true;
            _mainfunc = func;

            BreakPoints.Clear();
            _win.locvarlistwin.Show();
            foreach(LayoutDocument doc in _win.docs.Children)
            {
                var code = doc.Content as CodeEditor;
                BreakPoints.AddRange(code.MarkerService.BreakPoints);
            }

            CommandType = CommandTypes.Start;
            Application.Engine.SetTrace(Trace);
            Application.Engine.Execute($"pye.runcmd('{func}')", false);
            Enabled = false;
            _win.GetSelCode()?.MarkerService.ClearDebugMarker();
            _win.locvarlistwin.Hide();
        }

        public void Quit()
        {
            CommandType = CommandTypes.Quit;
        }

        public void Continue()
        {
            CommandType = CommandTypes.Continue;
        }

        public void Setp()
        {
            CommandType = CommandTypes.Setp;
        }

        public void Next()
        {
            CommandType = CommandTypes.Next;
        }

        public void Return()
        {
            CommandType = CommandTypes.Return;
        }

        public void Dispose()
        {
            if(Enabled) Quit();
        }

        #endregion

        public bool Enabled { get; private set; }

        private string _mainfunc;

        private BreakPoint _mbt;

        private BreakPoint _cbt;

        public BreakPointList BreakPoints { get; } = new BreakPointList();

    }
}

